package hu.elte.inf.holiuml.gui;

import hu.elte.inf.holiuml.model.ClassDiagram;
import hu.elte.inf.holiuml.model.ClassNode;
import hu.elte.inf.holiuml.model.CollaborationDiagram;
import hu.elte.inf.holiuml.model.CommentNode;
import hu.elte.inf.holiuml.model.ConnectionEdge;
import hu.elte.inf.holiuml.model.Diagram;
import hu.elte.inf.holiuml.model.Edge;
import hu.elte.inf.holiuml.model.Node;
import hu.elte.inf.holiuml.model.ObjectNode;
import hu.elte.inf.holiuml.model.RelationEdge;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.event.MouseInputAdapter;

/**
 * @author Csaba Hoch
 */
public class Canvas extends JPanel {

	static final int defaultSizeX = 500, defaultSizeY = 400;

	enum Event {
		MOUSE_MOVED, MOUSE_DRAGGED, MOUSE_LEFT_PRESSED, MOUSE_RIGHT_PRESSED, 
		MOUSE_RELEASED, KEY_ENTER, KEY_ESCAPE, KEY_DELETE, KEY_BACK_SPACE, 
		KEY_ASCII
	}

	/**
	 * The diagram that is currently drawn by the canvas.
	 */
	private Diagram diagram = null;

	/**
	 * It is true, if the user is drawing an edge between two nodes.
	 */
	private boolean edgeMode = false;

	/**
	 * The type of the edge that will be created. This is a meaningless value to
	 * the canvas. The value is created and used by the diagram, the canvas only
	 * stores it.
	 */
	private Diagram.EdgeType edgeType;
	private JPopupMenu popupMenu = new JPopupMenu();
	private CanvasMouseListener canvasMouseListener = new CanvasMouseListener();
	private Node currentNode = null;
	private Edge currentEdge = null;
	private Point oldPoint = null;
	private Point currentPoint = null;

	private HoliUML main;
	/**
	 * The {@link CanvasMouseListener} listens to the events and calls the
	 * eventHappened function of the canvas, when needed.
	 */
	class CanvasMouseListener extends MouseInputAdapter {

		private boolean listen = false;
		private boolean dragging = false;

		public void mouseMoved(MouseEvent e) {
			if (!listen) {
				return;
			}
			currentPoint = e.getPoint();
			eventHappened(Event.MOUSE_MOVED);
		}

		public void mousePressed(MouseEvent e) {
			if (!listen || dragging) {
				return;
			}
			if (e.getButton() == MouseEvent.BUTTON1) {
				currentPoint = e.getPoint();
				eventHappened(Event.MOUSE_LEFT_PRESSED);
				dragging = true;
			} else if (e.getButton() == MouseEvent.BUTTON3) {
				currentPoint = e.getPoint();
				eventHappened(Event.MOUSE_RIGHT_PRESSED);
				dragging = true;
			}
		}

		public void mouseReleased(MouseEvent e) {
			if (!listen || !dragging) {
				return;
			}
			if (e.getButton() == MouseEvent.BUTTON1
					|| e.getButton() == MouseEvent.BUTTON3) {
				currentPoint = e.getPoint();
				eventHappened(Event.MOUSE_RELEASED);
				dragging = false;
			}
		}

		public void mouseDragged(MouseEvent e) {
			if (!listen) {
				return;
			}
			currentPoint = e.getPoint();
			eventHappened(Event.MOUSE_DRAGGED);
		}

		public void mouseExited(MouseEvent e) {
			listen = false;
		}

		public void mouseEntered(MouseEvent e) {
			listen = true;
		}
	};

	public Canvas(HoliUML main) {
		this.main = main;
		setPreferredSize(new Dimension(defaultSizeX, defaultSizeY));
		addMouseListener(canvasMouseListener);
		addMouseMotionListener(canvasMouseListener);
	}

	public Diagram getDiagram() {
		return diagram;
	}

	public void setDiagram(Diagram diagram) {
		this.diagram = diagram;
		repaint();
	}

	/**
	 * Repaints the canvas.
	 */
	@Override
	protected void paintComponent(Graphics g) {
		g.setColor(Color.gray);
		g.fillRect(0, 0, getWidth(), getHeight());

		if (diagram != null) {
			g.setColor(Color.white);
			g.fillRect(0, 0, defaultSizeX, defaultSizeY);

			drawEdge(g);
			diagram.draw(g);
		}
	}

	/**
	 * Draws the edge that the user is trying to place. If there is no such edge
	 * or the first node is not selected, nothing will happen.
	 */
	private void drawEdge(Graphics g) {
		if (edgeMode && currentNode != null) {
			Node node = drawableUnderCursor();
			if (node != null) {
				g.setColor(Color.red);
				Edge.drawEdge(g, currentNode, node);
			} else {
				g.setColor(Color.gray);
				int x = currentNode.getX() + currentNode.getWidth() / 2;
				int y = currentNode.getY() + currentNode.getHeight() / 2;
				g.drawLine(x, y, currentPoint.x, currentPoint.y);
			}
		}
	}

	/**
	 * Returns the
	 */
	Node drawableUnderCursor() {
		return diagram.nodeUnderPoint(currentPoint);
	}

	void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
			eventHappened(Event.KEY_ESCAPE);
		else if (e.getKeyCode() == KeyEvent.VK_ENTER)
			eventHappened(Event.KEY_ENTER);
		else if (e.getKeyCode() == KeyEvent.VK_DELETE)
			eventHappened(Event.KEY_DELETE);
		else if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE)
			eventHappened(Event.KEY_BACK_SPACE);
	}

	void keyTyped(KeyEvent e) {
		if (e.getKeyChar() > 31) {
			// currentChar = e.getKeyChar();
			eventHappened(Event.KEY_ASCII);
		}
	}

	Point difference(Point p1, Point p2) {
		return new Point(p1.x - p2.x, p1.y - p2.y);
	}

	void eventHappened(Event e) {
		if(diagram != null){
			if (edgeMode) {
				if (e == Event.MOUSE_LEFT_PRESSED) {
					popupMenu.setVisible(false);
					Node node = drawableUnderCursor();
					if (node != null) {
						if (currentNode == null) {
							currentNode = node;
						} else {
							createNewEdge2(node);
						}
					}
				} else if (e == Event.MOUSE_MOVED) {
					repaint();
				} else if (e == Event.KEY_ESCAPE) {
					edgeMode = false;
					currentNode = null;
					repaint();
				}
			} else {
				if (currentNode == null) {
					if (e == Event.MOUSE_LEFT_PRESSED) {
						popupMenu.setVisible(false);
						currentNode = drawableUnderCursor();
						oldPoint = currentPoint;
					} else if (e == Event.MOUSE_RIGHT_PRESSED) {
						popupMenu.setVisible(false);
						Node node = diagram.nodeUnderPoint(currentPoint);
						if (node != null) {
							createPopupMenuOnNode(currentPoint.x, currentPoint.y, node);
						} else {
							Edge edge = diagram.edgeUnderPoint(currentPoint);
							if (edge != null) {
								currentEdge = edge;
								createPopupMenuOnEdge(currentPoint.x, currentPoint.y, edge);
							}
						}
					}
				} else {
					if (e == Event.MOUSE_RELEASED || e == Event.MOUSE_LEFT_PRESSED) {
						currentNode = null;
						repaint();
					} else if (e == Event.MOUSE_DRAGGED) {
						int x = currentPoint.x - oldPoint.x;
						int y = currentPoint.y - oldPoint.y;
						currentNode.move(x, y);
						oldPoint = currentPoint;
						repaint();
					}
				}
			}
		}
	}
	
	private void createPopupMenuOnEdge(int xCoordinate, int yCoordinate,  final Edge edge){
		popupMenu = new JPopupMenu();
		popupMenu.setLocation(currentPoint.x, currentPoint.y);
		JMenuItem deleteItem = new JMenuItem("Delete");
		JMenuItem modifyItem = new JMenuItem("Modify");
		if(edge.getClass().getSimpleName().equals("RelationEdge")){
			modifyItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					RelationEdgeOptionsDialog optDlg = new RelationEdgeOptionsDialog(
							main, (RelationEdge) edge, ((ClassDiagram) diagram)
									.getClassNodes());
					if (optDlg.getButtonCode() == OptionsDialog.OK) {
						optDlg.modifyData((RelationEdge)edge);
						Canvas.this.repaint();
					}
					popupMenu.setVisible(false);
				}
			});
			deleteItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					popupMenu.setVisible(false);
					int choise = JOptionPane.showConfirmDialog(main,"Are you sure and want to delete the selected edge?", "Delete", JOptionPane.YES_NO_OPTION);
					if(choise == 0){
						try{
							((ClassDiagram)diagram).removeRelationEdge((RelationEdge)edge);
						}catch(Exception exception){
							JOptionPane.showMessageDialog(main, "An error has occured while deleting the edge!", "Error!", JOptionPane.ERROR_MESSAGE);
						}
					}
				}
			});
		}else if(edge.getClass().getSimpleName().equals("ConnectionEdge")){
			modifyItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ConnectionEdgeOptionsDialog optDlg = new ConnectionEdgeOptionsDialog(
							main, (ObjectNode)((ConnectionEdge)edge).getNodeBegin(), (ObjectNode)((ConnectionEdge)edge).getNodeEnd(),
							((CollaborationDiagram) diagram).getObjectNodes(), (ConnectionEdge) edge);
					if (optDlg.getButtonCode() == OptionsDialog.OK) {
						optDlg.modifyConnection((ConnectionEdge)edge);
						Canvas.this.repaint();
					}
					popupMenu.setVisible(false);
				}
			});
			deleteItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					popupMenu.setVisible(false);
					int choise = JOptionPane.showConfirmDialog(main,"Are you sure and want to delete the selected edge?", "Delete", JOptionPane.YES_NO_OPTION);
					if(choise == 0){
						try{
							((CollaborationDiagram)diagram).removeConnectionEdge((ConnectionEdge)edge);
						}catch(Exception exception){
							JOptionPane.showMessageDialog(main, "An error has occured while deleting the edge!", "Error!", JOptionPane.ERROR_MESSAGE);
						}
					}
				}
			});
		}else if(edge.getClass().getSimpleName().equals("CommentEdge")){
			deleteItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					popupMenu.setVisible(false);
					int choise = JOptionPane.showConfirmDialog(main,"Are you sure and want to delete the selected edge?", "Delete", JOptionPane.YES_NO_OPTION);
					if(choise == 0){
						try{
							diagram.removeCommentEdge(edge);
						}catch(Exception exception){
							JOptionPane.showMessageDialog(main, "An error has occured while deleting the edge!", "Error!", JOptionPane.ERROR_MESSAGE);
						}
					}
				}
			});
		}
		if(edge.getClass().getSimpleName().equals("CommentEdge")){
			popupMenu.add(deleteItem);
		}else{
			popupMenu.add(deleteItem);
			popupMenu.addSeparator();
			popupMenu.add(modifyItem);
		}
		popupMenu.show(this, xCoordinate, yCoordinate);
	}
	
	private void createPopupMenuOnNode(int xCoordinate, int yCoordinate,  final Node node){
		popupMenu = new JPopupMenu();
		popupMenu.setLocation(currentPoint.x, currentPoint.y);
		JMenuItem deleteItem = new JMenuItem("Delete");
		JMenuItem modifyItem = new JMenuItem("Modify");
		if(node.getClass().getSimpleName().equals("ClassNode")){
			modifyItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ClassNodeOptionsDialog optDlg = new ClassNodeOptionsDialog(main, (ClassNode)node);
					if (optDlg.getButtonCode() == OptionsDialog.OK) {
						optDlg.modifyData((ClassNode)node);
						Canvas.this.repaint();
					}
					popupMenu.setVisible(false);
				}
			});
			deleteItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					popupMenu.setVisible(false);
					int choise = JOptionPane.showConfirmDialog(main,"Are you sure and want to delete the selected node?", "Delete", JOptionPane.YES_NO_OPTION);
					if(choise == 0){
						try{
							if(node.getClass().getSimpleName().equals("ClassNode")){
								((ClassDiagram)diagram).removeClassNode((ClassNode)node);
							}
						}catch(Exception exception){
							JOptionPane.showMessageDialog(main, "An error has occured while deleting the node!", "Error!", JOptionPane.ERROR_MESSAGE);
						}
					}
				}
			});
		}else if(node.getClass().getSimpleName().equals("ObjectNode")){
			modifyItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ObjectNodeOptionsDialog optDlg = new ObjectNodeOptionsDialog(main, (ObjectNode)node);
					if (optDlg.getButtonCode() == OptionsDialog.OK) {
						optDlg.modifyData((ObjectNode)node);
						Canvas.this.repaint();
					}
					popupMenu.setVisible(false);
				}
			});
			deleteItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					popupMenu.setVisible(false);
					int choise = JOptionPane.showConfirmDialog(main,"Are you sure and want to delete the selected node?", "Delete", JOptionPane.YES_NO_OPTION);
					if(choise == 0){
						try{
							if(node.getClass().getSimpleName().equals("ObjectNode")){
								((CollaborationDiagram)diagram).removeObjectNode((ObjectNode)node);
							}
						}catch(Exception exception){
							JOptionPane.showMessageDialog(main, "An error has occured while deleting the node!", "Error!", JOptionPane.ERROR_MESSAGE);
						}
					}
				}
			});
		}else if(node.getClass().getSimpleName().equals("CommentNode")){
			modifyItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					CommentOptionsDialog optDlg = new CommentOptionsDialog(main, (CommentNode)node);
					if (optDlg.getButtonCode() == OptionsDialog.OK) {
						optDlg.modifyCommentNode((CommentNode)node);
						Canvas.this.repaint();
					}
					popupMenu.setVisible(false);
				}
			});
			deleteItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					popupMenu.setVisible(false);
					int choise = JOptionPane.showConfirmDialog(main,"Are you sure and want to delete the selected node?", "Delete", JOptionPane.YES_NO_OPTION);
					if(choise == 0){
						try{
							diagram.removeCommentNode(node);
						}catch(Exception exception){
							JOptionPane.showMessageDialog(main, "An error has occured while deleting the node!", "Error!", JOptionPane.ERROR_MESSAGE);
						}
					}
				}
			});
		}
		popupMenu.add(deleteItem);
		popupMenu.addSeparator();
		popupMenu.add(modifyItem);
		popupMenu.show(this, xCoordinate, yCoordinate);
	}
	
	public void createNewEdge(Diagram.EdgeType edgeType) {
		this.edgeType = edgeType;
		edgeMode = true;
	}

	public void createNewEdge2(Node node2) {
		diagram.createNewEdge(currentNode, node2, edgeType);
		currentNode = null;
		edgeMode = false;
		repaint();
	}
}
